#ifndef _LOW_LEVEL_LAYER_H
#define _LOW_LEVEL_LAYER_H

#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestAssert.h>
#include <stdio.h>
#include "SuperBlockData.h"
#include "DiskAbstraction.h"
#include "Exception.h"
using std::cout;
using std::endl;


typedef unsigned int INodeId;
typedef enum INodeType_rec{NULL_TYPE=0, FILE_TYPE=1, DIRECTORY_TYPE=2, SOFT_LINK_TYPE=3} INodeType;
typedef struct INode_rec{
	char _fileType;
	char _hardLinksCount;
	unsigned int _fileSize;
	BlockId _directBlocks[10];
	int _indirectBlock;
} INode;

class LowLevelLayer
{
public:
	LowLevelLayer(string& fileName, unsigned int blockSize, unsigned int diskSize, unsigned int iNodesCount);
	~LowLevelLayer(void);
	//iNode Control
	enum ErrorCode{
		NO_FREE_INODES,
		INODE_NOT_INITIALIZED,
		ILLEGAL_INODE_TYPE,
		INODE_INDEX_OUT_OF_BOUNDS,
		INODE_BLOCK_INDEX_OUT_OF_BOUNDS,
		INODE_ILLEGAL_FILE_SIZE,
		NO_FREE_BLOCKS,
		BLOCKS_INDEX_OUT_OF_BOUNDS};
		INodeId allocateInode();
		void freeInode(INodeId iNodeId);
		INodeType getInodeType(INodeId iNodeId);
		void setInodeType(INodeId iNodeId, INodeType type);
		BlockId getDataBlock(INodeId iNodeId, unsigned int offset);
		void setDataBlock(INodeId iNodeId, unsigned int offset, BlockId blockId);
		unsigned int getFileSize(INodeId iNodeId);
		void setFileSize(INodeId iNodeId, unsigned int fileSize);


		//Block Control
		BlockId allocateDataBlock();
		void freeDataBlock(BlockId id);
		void readBlock(BlockId id, char* buffer);
		void writeBlock(BlockId id, char* newData);

		/*Note 	file names in dir-entries are kept without their path
		(so the file name of /ab/cd.out is just 'cd.out' ).
		Note  all services mentioned above should return appropriate error codes
		to stderr, when things go bad (for example: requesting to free an i-node
		that was not allocated, requesting to allocate an i-node when there are
		none free, asking for the file name of a free i-node, asking the 34th
		data block of a file that contains only 30 data blocks, etc.).
		*/

		unsigned int getBlockSize();
		INodeId getRootINode();
private:
	void writeINode(INodeId id, INode* iNode);
	void readINode(INodeId id, INode* iNode);
	void initNewInode(INodeId id);
	void freeINode(INodeId id);
private:
	DiskAbstraction* _da;
	SuperBlockData* _superBlock;
};

class LowLevelLayerTestCase : public CPPUNIT_NS::TestCase {
	CPPUNIT_TEST_SUITE(LowLevelLayerTestCase);
	CPPUNIT_TEST(testAllocateNodeSimple);
	CPPUNIT_TEST(testAllocateFreeBlock);
	CPPUNIT_TEST(testINodeType);
	CPPUNIT_TEST(testFileSize);
	CPPUNIT_TEST(testInodeDataBlock);
	CPPUNIT_TEST_SUITE_END();

public:
	void setUp(void) {
		cout<<endl;
	}
	void tearDown(void) {
		remove( "FILE_SYS" );
	}

protected:
	void testAllocateNodeSimple(void) {
		string fs("FILE_SYS");
		LowLevelLayer l(fs,64,6400,17);
		for(unsigned int i=0;i<17;i++){
			INodeId node1(l.allocateInode());
			CPPUNIT_ASSERT_EQUAL_MESSAGE("wrong inode id",(INodeId)i,node1);
			CPPUNIT_ASSERT_EQUAL_MESSAGE("iNode upon allocation should be file_type",FILE_TYPE,l.getInodeType(node1));
			CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong innitial inode size", (unsigned int)0, l.getFileSize(node1));
			for (unsigned int j=0;j<26;j++) {
				CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong innitial inode block pointer", (BlockId)0, l.getDataBlock(node1,j));
			}
		}
		//INodeId node2(l.allocateInode());
		try{
			l.allocateInode();
		}catch (Exception e){
			return;
		}
		CPPUNIT_FAIL("Should have thrown exception on no more free inodes");
	}
	void testAllocateFreeBlock(void) {
		string fs("FILE_SYS");
		LowLevelLayer l(fs,64,1350,1);
		for (int i=0;i<16;i++) {
			BlockId block(l.allocateDataBlock());
			CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong allocated block id",(BlockId)i+5,block);
			l.freeDataBlock(block);
		}
	}
	void testINodeType(void) {
		string fs("FILE_SYS");
		LowLevelLayer l(fs,64,1350,5);
		INodeId inode(l.allocateInode());
		l.setInodeType(inode,DIRECTORY_TYPE);
		CPPUNIT_ASSERT_EQUAL_MESSAGE("Inode type not set/get correctly",DIRECTORY_TYPE,l.getInodeType(inode));
		l.setInodeType(inode,SOFT_LINK_TYPE);
		CPPUNIT_ASSERT_EQUAL_MESSAGE("Inode type not set/get correctly",SOFT_LINK_TYPE,l.getInodeType(inode));
		l.setInodeType(inode,FILE_TYPE);
		CPPUNIT_ASSERT_EQUAL_MESSAGE("Inode type not set/get correctly",FILE_TYPE,l.getInodeType(inode));
		try{
			l.setInodeType(inode,NULL_TYPE);
		}catch(Exception ex){
			return;
		}
		CPPUNIT_FAIL("Should have thrown exception when trying to set inode type to null_type");
	}
	void testInodeDataBlock(void) {
		string fs("FILE_SYS");
		LowLevelLayer l(fs,64,6400,5);
		INodeId inode(l.allocateInode());
		for (unsigned int j=0;j<26;j++) {
			l.setDataBlock(inode,j,j+14);
		}
		for (unsigned int j=0;j<26;j++) {
			CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong inode data block set/get!!",j+14,l.getDataBlock(inode,j));
		}
	}
	void testFileSize(void) {
		string fs("FILE_SYS");
		LowLevelLayer l(fs,64,6400,5);
		INodeId inode(l.allocateInode());
 		l.setFileSize(inode,25);
		CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong inode file size!!",(unsigned int)25,l.getFileSize(inode));
	}
	/*void (void) {
	}
	void (void) {
	}
	void (void) {
	}
	void (void) {
	}
	void (void) {
	}
	void (void) {
	}
	void (void) {
	}
	void (void) {
	}
	void (void) {
	}
	void (void) {
	}
	void (void) {
	}
	void (void) {
	}
	void (void) {
	}
	void (void) {
	}
	void (void) {
	}
	void (void) {
	}*/
};

#endif
